﻿//-----------------------------------------------------------------------
// <copyright file="SP0001 - DisposeSPSiteAndSPWebObjectsCreated.cs" company="Adrien Siffermann">
//      Copyright © Adrien Siffermann 2008-2009. All rights reserved.
// </copyright>
// <summary>This file contains implementation of the DisposeSPSiteAndSPWebObjectsCreated class.</summary>
// <website>http://sovfxcoprules.codeplex.com/</website>
//-----------------------------------------------------------------------

namespace SharePoint.FxCop.BestPracticesRules
{
    using System;
    using System.Linq;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Defines a custom Code Analysis rule to check if the
    /// Dispose pattern is respected in the SharePoint Object Model
    /// when creating new SPSite or SPWeb objects
    /// </summary>
    public class DisposeSPSiteAndSPWebObjectsCreated : BaseStaticAnalysisRule
    {
        #region Private Members
        /// <summary>
        /// The counter of instructions.
        /// </summary>
        private int cpt;

        /// <summary>
        /// The instruction number of the last object instanciation.
        /// </summary>
        private int lastNew;

        /// <summary>
        /// The type of the last instanciated object.
        /// </summary>
        private string lastNewType = String.Empty;

        /// <summary>
        /// Indicates wether the last instruction was a constructor call.
        /// </summary>
        private bool isNewing;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="DisposeSPSiteAndSPWebObjectsCreated"/> class.
        /// </summary>
        public DisposeSPSiteAndSPWebObjectsCreated()
            : base("DisposeSPSiteAndSPWebObjectsCreated")
        {
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Checks the specified member.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The collection of problems.</returns>
        public override ProblemCollection Check(Member member)
        {
            if (!BaseStaticAnalysisRule.CheckMember(member))
            {
                return null;
            }
            
            Method method = member as Method;
            this.InitWorkingVars();
            this.VarsToWatch.Clear();
            this.isNewing = false;
            this.cpt = 0;
            this.lastNew = 0;
            this.lastNewType = String.Empty;

            foreach (Instruction instruction in method.Instructions)
            {
                switch (instruction.OpCode)
                {
                    case OpCode.Newobj:
                        this.CheckNewObj(instruction);
                        break;
                    case OpCode.Stloc_0:
                    case OpCode.Stloc_1:
                    case OpCode.Stloc_2:
                    case OpCode.Stloc_3:
                    case OpCode.Stloc_S:
                        this.CheckStLoc(instruction);
                        break;
                    case OpCode.Ldloc_0:
                    case OpCode.Ldloc_1:
                    case OpCode.Ldloc_2:
                    case OpCode.Ldloc_3:
                    case OpCode.Ldloc_S:
                        this.CheckLdLoc(instruction);
                        break;
                    case OpCode.Call:
                    case OpCode.Callvirt:
                    case OpCode.Jmp:
                        this.CheckCall(instruction);
                        break;
                    case OpCode.Ldstr:
                        break;
                    default:
                        this.InitWorkingVars();
                        this.isNewing = false;
                        break;
                }

                if (this.isNewing && this.cpt > this.lastNew)
                {
                    this.isNewing = false;
                    this.lastNew = 0;
                    Resolution resolution = this.GetNamedResolution("Base", method.FullName, this.lastNewType);
                    this.Problems.Add(new Problem(resolution, instruction));
                }

                this.cpt++;
            }

            this.AddProblems(method);
            return this.Problems;
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Checks if a new object of type SPSite or SPWeb is created.
        /// </summary>
        /// <param name="instruction">The instruction.</param>
        private void CheckNewObj(Instruction instruction)
        {
            Method ctor = instruction.Value as Method;
            string ctorType = ctor.FullName;
            int i = ctorType.LastIndexOf('#');
            if (i > 0)
            {
                ctorType = ctorType.Substring(0, i - 1);
            }

            if (ctorType == "Microsoft.SharePoint.SPSite" || ctorType == "Microsoft.SharePoint.SPWeb")
            {
                this.isNewing = true;
                this.lastNew = this.cpt;
                this.lastNewType = ctorType;
            }
        }

        /// <summary>
        /// Adds the local variable in the list to watch if it stores a new object of type SPSite or SPWeb.
        /// </summary>
        /// <param name="instruction">The instruction.</param>
        private void CheckStLoc(Instruction instruction)
        {
            if (this.isNewing)
            {
                Local varStored = instruction.Value as Local;
                this.VarsToWatch.Add(new WatchedVar() { Name = varStored.Name.Name, TypeFullName = varStored.Type.FullName, TargetedNode = instruction });
                this.isNewing = false;
                this.lastNew = 0;
            }
        }

        /// <summary>
        /// Checks the load of a watched local variable.
        /// </summary>
        /// <param name="instruction">The instruction.</param>
        private void CheckLdLoc(Instruction instruction)
        {
            Local varLoaded = instruction.Value as Local;
            WatchedVar watchedVarLoaded = this.VarsToWatch.FirstOrDefault(v => v.Name == varLoaded.Name.Name);
            if (watchedVarLoaded != null)
            {
                this.IsInvoking = true;
                this.CurrentVar = watchedVarLoaded;
            }
        }

        /// <summary>
        /// Checks the call of the Dispose method on a watched variable.
        /// </summary>
        /// <param name="instruction">The instruction.</param>
        private void CheckCall(Instruction instruction)
        {
            Method callee = instruction.Value as Method;
            string calleeName = callee.FullName;
            int j = calleeName.LastIndexOf('.');
            if (j > 0)
            {
                calleeName = calleeName.Substring(j + 1);
            }

            if (this.IsInvoking == true)
            {
                this.CheckDisposeCall(calleeName);
                this.IsInvoking = false;
            }
        }
        #endregion
    }
}
